import java.awt.Color;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.Random;

import javax.media.opengl.GL;

import com.sun.opengl.util.BufferUtil;


public class World implements VisibleObject 
{
	//create illusion of planet
	public static final int WORLD_RADIUS = 300; //will require aprox. 10mb height-map
	public static final int VERTICAL_DIVS = 150;
	public static final int HORIZONTAL_DIVS = 200;
	private final double PI = Math.PI;
	public final int GRID_VERTS = 4;
	public final int VERT_BYTES = 3 * BufferUtil.SIZEOF_BYTE;
	public final int VERT_FLOATS = 3* BufferUtil.SIZEOF_FLOAT;
	public final int GRID_BYTES = GRID_VERTS*VERT_BYTES;
	public final int GRID_FLOATS = GRID_VERTS * VERT_FLOATS;
	
	public static float[][] HEIGHT_MAP;
	public static Color[][] COLOR_MAP;
	private float maxHeight = 0,minHeight=WORLD_RADIUS-50;
	private Random random = new Random();
	
	public static boolean clicked=false;
	public static int clickX=-1,clickZ=-1;
	private int oldX=1,oldZ=1;
	public static boolean complete=false;
	public static int siteX=-1,siteZ=-1;
	private int siteCount;
	
	private FloatBuffer vertices, normals;
	private ByteBuffer colors;
	private int vertexCount;
	private int vertSize,normSize,colSize;
	int nA=0,nB=-1,nC=0;
	private int[] VBOdata=new int[1];

	
	public static int getGrid(double in)
	{
		return 0;//(int)Math.floor(in/(double)SQUARE_SIZE);
	}
	
	public void initGL(GL gl)
	{
		vertexCount = VERTICAL_DIVS*HORIZONTAL_DIVS*GRID_VERTS;
		
		vertSize = vertexCount*VERT_FLOATS;
		normSize = vertexCount*VERT_FLOATS;
		colSize = vertexCount*VERT_BYTES;
		
		vertices = getVerts();
		normals  = getNorms();
		colors  = getCols();
		gl.glGenBuffersARB(1, VBOdata,0);
		gl.glBindBuffer(GL.GL_ARRAY_BUFFER, VBOdata[0]);
		gl.glBufferData(GL.GL_ARRAY_BUFFER, (vertSize+normSize+colSize), null, GL.GL_STREAM_DRAW);
		gl.glBufferSubData(GL.GL_ARRAY_BUFFER, 0, vertSize, vertices);
		gl.glBufferSubData(GL.GL_ARRAY_BUFFER, vertSize, normSize, normals);
		gl.glBufferSubData(GL.GL_ARRAY_BUFFER, vertSize+normSize, colSize, colors);
		
		vertices = null;
		normals=null;
		colors=null;
		
		System.out.println("Buffers generated...");
		
		// Enable Pointers
        gl.glEnableClientState(GL.GL_COLOR_ARRAY);
        gl.glEnableClientState(GL.GL_VERTEX_ARRAY); 
        gl.glEnableClientState(GL.GL_NORMAL_ARRAY);
        
        //queen dig vars
        siteCount=0;
	}
	
	public void setHeightmap()
	{
		HEIGHT_MAP = new float[VERTICAL_DIVS][HORIZONTAL_DIVS];
		/**
		 * Generate heightmap like a fractal
		 * deviate height on area
		 * deviation decreases with area(length) decrease
		 */
		int its = 100;
		int inc=1;
		float length = 100;
		float heightDev;

		
		int baseHeight = WORLD_RADIUS;
		for(int h=0;h<HORIZONTAL_DIVS;h++)
		{
			for(int v=0;v<VERTICAL_DIVS;v++)
			{
				HEIGHT_MAP[v][h] = baseHeight;
			}
		}
		maxHeight=100;
		
		for(int i=2;i<its;i+=inc)
		{
			length = its/(float)i;
			System.out.println("length: "+length);
			for(int h=0;h<HORIZONTAL_DIVS;h+=length)
			{
				for(int v=0;v<VERTICAL_DIVS;v+=length)
				{
					heightDev = (float)(random.nextGaussian()*1.618*Math.sqrt(length));
					for(int hSet=h;hSet<h+length;hSet++)
					{
						for(int vSet=v;vSet<v+length;vSet++)
						{
							if(vSet>=VERTICAL_DIVS||hSet>=HORIZONTAL_DIVS)break;
							HEIGHT_MAP[vSet][hSet] += heightDev;
							if(HEIGHT_MAP[vSet][hSet]<minHeight)HEIGHT_MAP[vSet][hSet]=minHeight;
							else if(HEIGHT_MAP[vSet][hSet]>maxHeight)maxHeight=HEIGHT_MAP[vSet][hSet];
						}
					}
				}
			}
			//smoothen
			float tempHeight;
			float hFactor;
			for(int h=1;h<HORIZONTAL_DIVS-1;h++)
			{
				for(int v=1;v<VERTICAL_DIVS-1;v++)
				{
					tempHeight = HEIGHT_MAP[v][h];
					hFactor = Math.abs(HEIGHT_MAP[v][h]/maxHeight*10);
					HEIGHT_MAP[v][h] = (tempHeight*hFactor+HEIGHT_MAP[v+1][h]+HEIGHT_MAP[v-1][h]+HEIGHT_MAP[v][h+1]+HEIGHT_MAP[v][h-1])/(4+hFactor);
				}
			}
			inc++;
		}
		
	}
	
	public void setColMap()//this method cannot be called before heightmap was made
	{
		COLOR_MAP = new Color[VERTICAL_DIVS][HORIZONTAL_DIVS];
		float temp;
		for(int h=0;h<HORIZONTAL_DIVS;h++)
		{
			for(int v=0;v<VERTICAL_DIVS;v++)
			{
				temp =HEIGHT_MAP[v][h]/maxHeight;
				COLOR_MAP[v][h] = new Color(Color.HSBtoRGB(temp, temp, temp/1.618f));
			}
		}
	}
	
	public void click(int xIn,int zIn)
	{
		if(clickX!=-1&&clickZ!=-1){oldX=clickX;oldZ=clickZ;}
		clickX = xIn;
		clickZ = zIn;
		clicked=true;
	}
	
	public void display(GL gl)
	{
		gl.glEnable(GL.GL_COLOR_MATERIAL);
		gl.glBindBuffer(GL.GL_ARRAY_BUFFER, VBOdata[0]);
		
		ByteBuffer mapped =  gl.glMapBuffer(GL.GL_ARRAY_BUFFER, GL.GL_READ_WRITE);
		if(mapped!=null)
		{
			/**			
			temp.put(getVerts(offset));
			temp.put(getNorms());	
			*/		
			if(complete)
			{				
				if(siteCount>500)
				{
					updateCols(mapped,clickX-1,clickZ-1,3,3,false);
					complete=false;
					siteCount=0;
				}
				else
				{
					FloatBuffer temp = mapped.asFloatBuffer();
					updateVerts(temp,siteX,siteZ);
					mapped.position(vertSize+normSize);
				}								
			}
			if(clicked)
			{
				updateCols(mapped,oldX-1,oldZ-1,3,3,false);
				updateCols(mapped,clickX-1,clickZ-1,3,3,true);
				complete=false;
				clicked = false;
			}
			gl.glUnmapBuffer(GL.GL_ARRAY_BUFFER);
		}			
		
        gl.glVertexPointer(3, GL.GL_FLOAT, 0, 0);
        gl.glNormalPointer(GL.GL_FLOAT, 0, vertSize);        
        gl.glColorPointer(3, GL.GL_BYTE, 0, vertSize+normSize);
        
        // Render
        // Draw All Of The Triangles At Once
        gl.glDrawArrays(GL.GL_QUADS, 0, vertexCount);  
        
        //Release pointers
        gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0);
        gl.glDisable(GL.GL_COLOR_MATERIAL);
	}

	public FloatBuffer getVerts()
	{
		double phi,phi2, theta,theta2;
		double sP,sP2, cP,cP2, sT,sT2,cT,cT2;
		double radius;
		int v,v2,h,h2;
		FloatBuffer temp=BufferUtil.newFloatBuffer(vertexCount*3);
		for(h=0;h<HORIZONTAL_DIVS;h++)//precessing theta
		{
			h2= h+1;
		
			theta = (h*2*PI)/(double)HORIZONTAL_DIVS;
			sT = Math.sin(theta);
			cT = Math.cos(theta);
			theta2= ((h2)*2*PI)/(double)HORIZONTAL_DIVS;
			sT2 = Math.sin(theta2);
			cT2 = Math.cos(theta2);
			if(h2==HORIZONTAL_DIVS)h2=0;
			for(v=0;v<VERTICAL_DIVS;v++)//precessing phi
			{	
				v2=v+1;
				
				phi = (v*PI)/(double)VERTICAL_DIVS;
				sP = Math.sin(phi);
				cP = Math.cos(phi);
				phi2 = ((v2)*PI)/(double)VERTICAL_DIVS;
				sP2 = Math.sin(phi2);
				cP2 = Math.cos(phi2);
				if(v2>=VERTICAL_DIVS)v2=0;
				
				radius = HEIGHT_MAP[v][h];
				//base
				System.out.println("x: "+(int)(radius*sP*cT)+",y: "+(int)(radius*sP*sT)+", z: "+(int)(radius*cP));
				temp.put((float)(radius*sP*cT));
				temp.put((float)(radius*sP*sT));
				temp.put((float)(radius*cP));	
				
				radius = HEIGHT_MAP[v2][h];
				
				temp.put((float)(radius*sP2*cT));
				temp.put((float)(radius*sP2*sT));
				temp.put((float)(radius*cP2));	
				
				radius = HEIGHT_MAP[v2][h2];
				
				temp.put((float)(radius*sP2*cT2));
				temp.put((float)(radius*sP2*sT2));
				temp.put((float)(radius*cP2));	
				
				radius = HEIGHT_MAP[v][h2];
				
				temp.put((float)(radius*sP*cT2));
				temp.put((float)(radius*sP*sT2));
				temp.put((float)(radius*cP));	
			}
		}
		temp.flip();
		return temp;
	}
	
	public void updateVerts(FloatBuffer mapIn, int xIn, int zIn)
	{
		siteCount++;
		for(int h=-1;h<=1;h++)
		{
			for(int w=-1;w<=1;w++)
			{
				HEIGHT_MAP[xIn+w][zIn+h] -= 0.1f;
			}
		}
		//adjust 9 quads to get one dig site outer vertexes stay on the same place
		/**
		mapIn.rewind();
		mapIn.position(((zIn-1)*WORLD_SIZE+xIn-1)*GRID_VERTS*3);
		fillTop(mapIn, xIn, zIn);
		mapIn.rewind();
		mapIn.position((zIn*WORLD_SIZE+xIn-1)*GRID_VERTS*3);
		fillMid(mapIn,xIn,zIn);
		mapIn.rewind();
		mapIn.position(((zIn+1)*WORLD_SIZE+xIn-1)*GRID_VERTS*3);
		fillBot(mapIn,xIn,zIn);
		*/
		mapIn.flip();
	}
	/**
	public void fillTop(FloatBuffer src, int xIn,int zIn)
	{
		//top-left quad
		src.get();
		src.get();
		src.get();
		
		src.get();
		src.get();
		src.get();
		
		src.put((xIn-1)*SQUARE_SIZE);
		src.put(HEIGHT_MAP[xIn][zIn]);
		src.put((zIn-1)*SQUARE_SIZE);
		
		src.get();
		src.get();
		src.get();
		
		//top-mid quad
		src.get();
		src.get();
		src.get();
		
		src.put((xIn-1)*SQUARE_SIZE);
		src.put(HEIGHT_MAP[xIn][zIn]);
		src.put((zIn-1)*SQUARE_SIZE);
		
		src.put((xIn+2)*SQUARE_SIZE);
		src.put(HEIGHT_MAP[xIn][zIn]);
		src.put((zIn-1)*SQUARE_SIZE);
		
		src.get();
		src.get();
		src.get();
		
		//top-right
		src.get();
		src.get();
		src.get();
		
		src.put((xIn+2)*SQUARE_SIZE);
		src.put(HEIGHT_MAP[xIn][zIn]);
		src.put((zIn-1)*SQUARE_SIZE);
		
		src.get();
		src.get();
		src.get();
		
		src.get();
		src.get();
		src.get();
	}
	
	public void fillMid(FloatBuffer src, int xIn,int zIn)
	{
		//mid-left quad
		src.get();
		src.get();
		src.get();
		
		src.get();
		src.get();
		src.get();
		
		src.put((xIn-1)*SQUARE_SIZE);
		src.put(HEIGHT_MAP[xIn][zIn]);
		src.put((zIn+2)*SQUARE_SIZE);
		
		src.put((xIn-1)*SQUARE_SIZE);
		src.put(HEIGHT_MAP[xIn][zIn]);
		src.put((zIn-1)*SQUARE_SIZE);
		
		//mid-mid quad		
		src.put((xIn-1)*SQUARE_SIZE);
		src.put(HEIGHT_MAP[xIn][zIn]);
		src.put((zIn-1)*SQUARE_SIZE);
		
		src.put((xIn-1)*SQUARE_SIZE);
		src.put(HEIGHT_MAP[xIn][zIn]);
		src.put((zIn+2)*SQUARE_SIZE);
		
		src.put((xIn+2)*SQUARE_SIZE);
		src.put(HEIGHT_MAP[xIn][zIn]);
		src.put((zIn+2)*SQUARE_SIZE);
		
		src.put((xIn+2)*SQUARE_SIZE);
		src.put(HEIGHT_MAP[xIn][zIn]);
		src.put((zIn-1)*SQUARE_SIZE);
		
		//mid-right
		src.put((xIn+2)*SQUARE_SIZE);
		src.put(HEIGHT_MAP[xIn][zIn]);
		src.put((zIn-1)*SQUARE_SIZE);
		
		src.put((xIn+2)*SQUARE_SIZE);
		src.put(HEIGHT_MAP[xIn][zIn]);
		src.put((zIn+2)*SQUARE_SIZE);
		
		src.get();
		src.get();
		src.get();
		
		src.get();
		src.get();
		src.get();
	}
	
	public void fillBot(FloatBuffer src, int xIn,int zIn)
	{
		//bot-left quad
		src.get();
		src.get();
		src.get();
		
		src.get();
		src.get();
		src.get();
		
		src.get();
		src.get();
		src.get();
		
		src.put((xIn-1)*SQUARE_SIZE);
		src.put(HEIGHT_MAP[xIn][zIn]);
		src.put((zIn+2)*SQUARE_SIZE);
		
		//bot-mid quad		
		src.put((xIn-1)*SQUARE_SIZE);
		src.put(HEIGHT_MAP[xIn][zIn]);
		src.put((zIn+2)*SQUARE_SIZE);
		
		src.get();
		src.get();
		src.get();
		
		src.get();
		src.get();
		src.get();
		
		src.put((xIn+2)*SQUARE_SIZE);
		src.put(HEIGHT_MAP[xIn][zIn]);
		src.put((zIn+2)*SQUARE_SIZE);
		
		//bot-right
		src.put((xIn+2)*SQUARE_SIZE);
		src.put(HEIGHT_MAP[xIn][zIn]);
		src.put((zIn+2)*SQUARE_SIZE);
		
		src.get();
		src.get();
		src.get();
		
		src.get();
		src.get();
		src.get();
		
		src.get();
		src.get();
		src.get();
	}
	*/
	
	public FloatBuffer getNorms()
	{
		FloatBuffer temp=BufferUtil.newFloatBuffer(vertexCount*3);

		for(int h=0;h<HORIZONTAL_DIVS;h++)
		{
			for(int v=0;v<VERTICAL_DIVS;v++)
			{
				//base
				temp.put(nA);
				temp.put(nB);
				temp.put(nC);	
				
				temp.put(nA);
				temp.put(nB);
				temp.put(nC);
				
				temp.put(nA);
				temp.put(nB);
				temp.put(nC);
				
				temp.put(nA);
				temp.put(nB);
				temp.put(nC);
			}
		}
		temp.flip();
		return temp;
	}
		
	public ByteBuffer getCols()
	{
		ByteBuffer temp=BufferUtil.newByteBuffer(vertexCount*3);
		
		for(int h=0;h<HORIZONTAL_DIVS;h++)
		{
			for(int v=0;v<VERTICAL_DIVS;v++)
			{
				for(int p=0;p<4;p++)
				{
					temp.put( (byte)COLOR_MAP[v][h].getRed()   );
					temp.put( (byte)COLOR_MAP[v][h].getGreen() );
					temp.put( (byte)COLOR_MAP[v][h].getBlue()  );
				}
			}
		}
		temp.flip();
		return temp;
	}
	
	public void updateCols(ByteBuffer mapIn, int xIn, int zIn, int wIn, int hIn, boolean on)
	{
		ByteBuffer temp= BufferUtil.newByteBuffer(wIn*GRID_BYTES);
		int offset;
		for(int z=zIn;z<zIn+hIn;z++)
		{
			temp.rewind();
			for(int w=0;w<wIn;w++)
			{
				for(int p=0;p<4;p++)
				{	
					if(on)
					{
						temp.put((byte)-27);
						temp.put((byte)15);
						temp.put((byte)-5);
					}
					else
					{
						temp.put((byte)(COLOR_MAP[xIn+w][z].getRed()));
						temp.put((byte)(COLOR_MAP[xIn+w][z].getGreen()));
						temp.put((byte)(COLOR_MAP[xIn+w][z].getBlue()));
					}
				}
			}
			temp.flip();
			offset = (vertSize+normSize)+(z*VERTICAL_DIVS+xIn)*GRID_BYTES;
			mapIn.position(offset);
			mapIn.put(temp);
			
		}
	}
	

}
